package com.ziytek.web.citizen.config.caffeine;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import com.github.benmanes.caffeine.cache.Scheduler;
import com.ziytek.web.citizen.configuration.PoolConfig;
import com.ziytek.web.citizen.util.CabinetNoUtil;
import io.micrometer.common.lang.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.index.qual.NonNegative;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author fengh
 */
@Slf4j
public class CaffeineUtil {

    public static final String JOB_TIMES = "job_times_";
    public static final String STATS_CACHE_PREFIX = "stats_";
    public static final String IP_CACHE_PREFIX = "clientIp_";
    public static final String CURRENT_CACHE_PREFIX = "current_";
    public static final String FIRST_CURRENT_CACHE_PREFIX = "first_current_";
    public static final String OFFLINE_CACHE_PREFIX = "offline_";
    public static final String WEB_IP_CACHE_PREFIX = "webClientIp_";
    public static final String FIRST_DATA_CACHE_PREFIX = "first_data_";
    public static final String FIRST_DATA_ALL_CACHE_PREFIX = "first_all_";
    public static final String INTERCEPTOR_INSERT = "interceptor_insert_";
    public static final String INTERRUPT_CAPACITY_KEY = "interrupt_stats_";
    public static final String FIRST_DATA_LIST_KEY = "first_data_list_key_";
    public static final String SQLITE_SWITCH_CACHE_PREFIX = "sqlite_switch_";
    public static final String COMMAND_CONFIRMATION = "command_confirmation_";
    public static final String FIRST_DATA_SINGLE_CACHE_PREFIX = "first_single_";
    public static final String COMMAND_CONFIRMATION_ID = "id_command_confirmation_id_";
    public static final String BOARD_ABNORMAL_KEY = "board_abnormal_key_";
    public static final String CHANNEL_ABNORMAL_KEY_ = "channel_abnormal_key_";
    public static final String FIRST_DATA_CURRENT_CACHE_KEY = "first_data_current_cache_key_";
    public static final String FIRST_DATA_VOLTAGE_CACHE_KEY = "first_data_voltage_cache_key_";
    public static final String FIRST_DATA_NOT_NORMAL_CACHE_KEY = "first_data_not_normal_cache_key_";
    public static final String OPERATION_DETAIL_DEL_KEY = "operationDetailDel_";
    public static final String BATCH_NO_KEY = "batch_no_key_";
    public static final String R_CAP_STATS_VO_KEY = "r_cap_stats_vo_key_";

    public static final String CACHE_KEY_SYMBOL = "&";
    public static final String CACHE_KEY_COMMAND = "command_";
    public static final String CACHE_KEY_COMMAND_FIRST = "first_command_";
    public static final String CACHE_KEY_OFFLINE = "offline_";
    public static final String CACHE_KEY_ONLINE = "online_";
    public static final String CACHE_KEY_DEVICE_OFFLINE_YS = "deviceOffline_";
    public static final String WEB_KEY_OFFLINE = "webOffline_";
    public static final String CACHE_KEY_DOORS_NO = "doors_no_";
    public static final String CACHE_KEY_DOORS_NO_PAIR = "doors_no_pair_";
    public static final String CACHE_KEY_CONNECTION_SYMBOL = "_";
    public static final String CACHE_KEY_DOORS_CONFIG = "doors_config_";
    public static final String CACHE_KEY_COMMAND_OPERATE = "command_operate_";
    public static final String CACHE_KEY_COMMAND_OPERATE_ALL = "command_operate_all_";
    public static final String CACHE_KEY_TOPIC_REALTIME_QUERY = "topic_realtime_query_";
    public static final String CACHE_KEY_TOPIC_CAB_STATUS = "topic_cab_status_";
    public static final String CACHE_KEY_TOPIC_REAL_STATS_QUERY = "topic_realStats_query_";
    public static final String CACHE_KEY_TOPIC_REAL_STATS_FIRST_PUSH = "topic_realStats_first_push_";

    public static String getCacheKey(int cabinetNo, int boardNo, int channelNo, int order) {
        return CabinetNoUtil.getCabinetNo(String.valueOf(cabinetNo))
                + CACHE_KEY_SYMBOL
                + boardNo
                + CACHE_KEY_CONNECTION_SYMBOL
                + channelNo
                + CACHE_KEY_CONNECTION_SYMBOL
                + order;
    }

    public static String getCacheKey(int cabinetNo, int boardNo, int channelNo) {
        return CabinetNoUtil.getCabinetNo(String.valueOf(cabinetNo))
                + CACHE_KEY_SYMBOL
                + boardNo
                + CACHE_KEY_CONNECTION_SYMBOL
                + channelNo;
    }


    public static class CacheObject<T> {
        /**
         * 缓存数据
         */
        public T data;
        /**
         * 过期时间 单位:纳秒
         */
        public long expire;

        public CacheObject(T data, long second) {
            this.data = data;
            this.expire = TimeUnit.SECONDS.toNanos(second);
        }
    }

    /**
     * 不设置过期时长,即默认时间为5分钟
     */
    public static final long NOT_EXPIRE = 5 * 60;

    private static Expiry<String, CacheObject<?>> expireAfterExpiry() {
        return new Expiry<String, CacheObject<?>>() {
            @Override
            public long expireAfterCreate(@NonNull String key, @NonNull CacheObject<?> value, long currentTime) {
                //将过期时间设置为自定义的过期时间
                return value.expire;
            }

            @Override
            public long expireAfterUpdate(@NonNull String key, @NonNull CacheObject<?> value, long currentTime, @NonNegative long currentDuration) {
                return value.expire;
            }

            @Override
            public long expireAfterRead(@NonNull String key, @NonNull CacheObject<?> value, long currentTime, @NonNegative long currentDuration) {
                return currentDuration;
            }
        };
    }


    public static final Cache<String, CacheObject<?>> CAFFEINE = Caffeine.newBuilder()
            .expireAfter(expireAfterExpiry())
            .initialCapacity(100)
            .maximumSize(124000)
            .scheduler(Scheduler.forScheduledExecutorService(PoolConfig.EXECUTOR))
            .removalListener(CacheRemovalListener::onRemoval)
            .build();

    public static final Cache<String, CacheObject<?>> BASIC_CAFFEINE = Caffeine.newBuilder()
            .expireAfter(expireAfterExpiry())
            .initialCapacity(1)
            .maximumSize(20000)
            .scheduler(Scheduler.forScheduledExecutorService(PoolConfig.EXECUTOR))
            .removalListener(CacheRemovalListener::onRemoval)
            .build();


    public static void setBasic(String key, Object value) {
        BASIC_CAFFEINE.put(key, new CacheObject<>(value, 5));
    }

    public static <T> void set(String key, T value, long expire) {
        CAFFEINE.put(key, new CacheObject<>(value, expire));
    }

    public static void set(String key, Object value) {
        set(key, value, NOT_EXPIRE);
    }

    public static <T> T get(String key) {
        CacheObject<?> cacheObject = CAFFEINE.getIfPresent(key);
        return cacheObject == null ? null : (T) cacheObject.data;
    }

    public static boolean containsKey(String key) {
        return CAFFEINE.getIfPresent(key) != null;
    }


    public static void delete(String key) {
        CAFFEINE.invalidate(key);
    }

    public static void deleteStartWith(String key) {
        List<String> keys = new ArrayList<>();
        CAFFEINE.asMap().forEach((k, v) -> {
            if (k.startsWith(key)) {
                keys.add(k);
            }
        });

        if (CollectionUtils.isEmpty(keys)) {
            //log.error("删除缓存失败,以key[{}]开头的key不存在", key);
            return;
        }
        delete(keys);
    }

    public static Map<String, CacheObject<?>> getAllStartWith(String keyPrefix) {
        Map<String, CacheObject<?>> map = new HashMap<>();
        CAFFEINE.asMap().forEach((k, v) -> {
            if (k.startsWith(keyPrefix)) {
                map.put(k, v);
            }
        });

        return map;
    }


    public static void delete(Collection<String> keys) {
        CAFFEINE.invalidateAll(keys);
    }

}